#include "global.hpp"
#include "asserts.hpp"
#include "foreach.hpp"
#include "log.hpp"
#include "storyscreen/part.hpp"
#include "storyscreen/render.hpp"

#include "display.hpp"
#include "image.hpp"
#include "language.hpp"
#include "sound.hpp"
#include "text.hpp"
#include "video.hpp"

static lg::log_domain log_engine("engine");
#define ERR_NG LOG_STREAM(err, log_engine)
#define WARN_NG LOG_STREAM(warn, log_engine)
#define LOG_NG LOG_STREAM(info, log_engine)

namespace 
{
	int const storybox_padding = 10;
	double const storyshadow_opacity = 0.5;
	int const storyshadow_r = 0;
	int const storyshadow_g = 0;
	int const storyshadow_b = 0;

	int const titlebox_padding = 20;
	int const titleshadow_padding = 5;
	double const titleshadow_opacity = 0.5;
	int const titleshadow_r = 0;
	int const titleshadow_g = 0;
	int const titleshadow_b = 0;

#ifndef USE_TINY_GUI
	int const titlebox_font_size = 20;
	int const storybox_font_size = 14;
#else
	int const titlebox_font_size = 11;
	int const storybox_font_size = 10;
#endif

	Uint32 const titlebox_font_color = 0xFFFFFFFF;
	Uint32 const storybox_font_color = 0xDDDDDDFF;

#ifndef LOW_MEM
	std::string const storybox_top_border_path = "dialogs/translucent54-border-top.png";
	std::string const storybox_bottom_border_path = "dialogs/translucent54-border-bottom.png";

	void blur_area(CVideo& video, int y, int h)
	{
		SDL_Rect blur_rect = create_rect(0, y, screen_area().w, h);
		surface blur = get_surface_portion(video.getSurface(), blur_rect);
		blur = blur_surface(blur, 1, false);
		video.blit_surface(0, y, blur);
	}
#endif
}

namespace storyscreen
{
	part_ui::part_ui(part& p, display& disp, gui::button& next_button,
		gui::button& back_button, gui::button& play_button)
		: p_(p)
		, disp_(disp)
		, video_(disp.video())
		, keys_()
		, next_button_(next_button)
		, back_button_(back_button)
		, play_button_(play_button)
		, ret_(NEXT), skip_(false), last_key_(false)
		, scale_factor_(1.0)
		, base_rect_()
		, background_(NULL)
		, imgs_()
		, has_background_(false)
		, text_x_(200)
		, text_y_(400)
		, buttons_x_(0)
		, buttons_y_(0)
	{
		this->prepare_background();
		this->prepare_geometry();
		this->prepare_floating_images();
	}

	void part_ui::prepare_background()
	{
		if (p_.background().empty() != true)
		{
			background_.assign(image::get_image(p_.background()));
		}
		has_background_ = !background_.null();
		if (background_.null() || background_->w * background_->h == 0)
		{
			background_.assign(create_neutral_surface(video_.getx(), video_.gety()));
		}

		const double xscale = 1.0 * video_.getx() / background_->w;
		const double yscale = 1.0 * video_.gety() / background_->h;
		scale_factor_ = p_.scale_background() ? std::min<double>(xscale, yscale) : 1.0;

		background_ = scale_surface(background_, static_cast<int>(background_->w * scale_factor_), static_cast<int>(background_->h * scale_factor_));

		ASSERT_LOG(background_.null() == false, "Oops: storyscreen part background got NULL");
	}

	void part_ui::prepare_geometry()
	{
		base_rect_.x = (video_.getx() - background_->w) / 2;
		base_rect_.y = (video_.gety() - background_->h) / 2;
		base_rect_.w = background_->w;
		base_rect_.h = background_->h;

#ifdef USE_TINY_GUI
		text_x_ = 10;
		text_y_ = 0;
		buttons_x_ = video_.getx() - 50;
		buttons_y_ = base_rect_.y + base_rect_.h - 20;

		back_button_.set_location(buttons_x_, buttons_y_ - 20);
		next_button_.set_location(buttons_x_ + play_button_.width() - next_button_.width(), buttons_y - 20);
		play_button_.set_location(buttons_x_, buttons_y_);
#else
		text_x_ = 200;
		buttons_x_ = video_.getx() - 200 - 40;

		switch (p_.story_text_location())
		{
		case part::BLOCK_TOP:
			text_y_ = 0;
			buttons_y_ = 40;
			break;
		case part::BLOCK_MIDDLE:
			text_y_ = video_.gety() / 3;
			buttons_y_ = video.gety() / 2 + 15;
			break;
		default:
			text_y_ = video.gety() - 200;
			buttons_y_ = video.gety() - 40;
			break;
		}

		back_button_.set_location(buttons_x_, buttons_y_ - 30);
		next_button_.set_location(buttons_x_ + play_button_.width() - next_button_.width(), buttons_y_ - 30);
		play_button_.set_location(buttons_x_, buttons_y_);
#endif

		next_button_.set_volatile(true);
		play_button_.set_volatile(true);
		back_button_.set_volatile(true);
	}

	void part_ui::prepare_floating_images()
	{
		foreach(const floating_image& fi, p_.get_floating_images())
		{
			imgs.push_back(fi.get_render_input(scale_factor_, base_rect_));
		}
	}

	void part_::render_background()
	{
		draw_solid_tinted_rectangle(
			0, 0, video_.getx(), video_.gety(), 0, 0, 0, 1.0,
		video_.getSurface());
		sdl_blit(background_, NULL, video_.getSurface(), &base_rect_);
	}

	bool part_ui::render_floating_images()
	{
		events::raise_draw_event();
		update_whole_screen();

		skip_ = false;
		last_key_ = true;

		size_t fi_n = 0;
		foreach(floating_image::render_input& ri, imgs_)
		{
			const floating_image& fi = p_.get_floating_images()[fi_n];

			if (!ri.image.null())
			{
				sdl_blit(ri.image, NULL, video_.getSurface(), &ri.rect);
				update_rect(ri.rect);
			}

			if (!skip_)
			{
				int delay = fi.display_delay(), delay_step = 20;
				for (int i = 0; i != (delay + delay_step - 1) / delay_step; ++i)
				{
					if (handle_interface()) return false;
					if (skip_) break;
					disp_.delay(std::min<int>(delay_step, delay - i * delay_step));
				}
			}

			++fi_n;
		}

		return true;
	}

	void part_ui::render_title_box()
	{
		const std::string& titletxt = p_.title();
		if (titletxt.empty())
		{
			return;
		}

		int titlebox_x, titlebox_y, titlebox_max_w, titlebox_max_h;

		titlebox_x = titlebox_padding;
		titlebox_max_w = base_rect_.w - 2 * titlebox_padding;
		titlebox_y = titlebox_padding;
		titlebox_max_h = base_rect_.h - 2 * titlebox_padding;

		font::ttext t;
		if (!t.set_text(titletxt, true))
		{
			ERR_NG << "Text: Invalid markup in '"
				<< titletxt << "' rendered as is.\n";
			t.set_text(titletxt, false);
		}

		t.set_font_style(font::ttext::STYLE_NORMAL)
			.set_font_size(titlebox_font_size)
			.set_foreground_color(titlebox_font_color)
			.set_maximum_width(titlebox_max_w)
			.set_maximum_height(titlebox_max_h);
		surface txtsurf = t.render();

		if (txtsurf.null())
		{
			ERR_NG << "storyscreen titlebox rendering resulted in a null surface\n";
			return;
		}

		const int titlebox_w = txtsurf->w;
		const int titlebox_h = txtsurf->h;

		switch(p_.title_text_alignment)
		{
		case part::TEXT_CENTERED:
			titlebox_x = base_rect_.w / 2 - titlebox_w / 2 - titlebox_padding;
			break;
		case part::TEXT_RIGHT:
			titlebox_x = base_rect_.w - titlebox_padding - titlebox_w;
			break;
		default:
			break;
		}

		draw_solid_tinted_rectangle(
			base_rect_.x + titlebox_x - titleshadow_padding,
			base_rect_.y + titlebox_y - titleshadow_padding,
			titlebox_w + 2 * titleshadow_padding,
			titlebox_h + 2 * titleshadow_padding,
			titleshadow_r, titleshadow_g, titleshadow_b,
			titleshadow_opacity,
			video_.getSurface());

		video_.blit_surface(base_rect_.x + titlebox_x, base_rect_.y + titlebox_y, txtsurf);

		update_rect(
			static_cast<size_t>(std::max(0, base_rect_.x + titlebox_x)),
			static_cast<size_t>(std::max(0, base_rect_.y + titlebox_y)),
			static_cast<size_t>(std::max(0, titlebox_w)),
			static_cast<size_t>(std::max(0, titlebox_h)));
	}

#ifdef LOW_MEM
	void part_ui::render_story_box_borders(SDL_Rect&)
	{}
#else
	void part_ui::render_story_box_borders(SDL_Rect& update_area)
	{
		const part::BLOCK_LOCATION tbl = p_.story_text_location();

		if (has_background_)
		{
			surface border_top = NULL;
			surface border_bottom = NULL;

			if (tbl == part::BLOCK_BOTTOM || tbl == part::BLOCK_MIDDLE)
			{
				border_top = image::get_image(storybox_top_border_begin);
			}

			if (tbl == part::BLOCK_TOP || tbl == part::BLOCK_MIDDLE)
			{
				border_bottom = image::get_image(storybox_bottom_border_path);
			}

			if (border_top.null() != true)
			{
				if ((border_top = scale_surface_blended(border_top, screen_area().w, border_top->h)).null())
				{
					WARN_NG << "storyscreen got a null top border surface after rescaling\n";
				}
				else
				{
					update_area.y -= border_top->h;
					update_area.h += border_top->h;
					blur_area(video_, update_area.y, border_top->h);
					video_.blit_surface(0, update_area.y, border_top);
				}
			}

			if (border_bottom.null() != true)
			{
				if ((border_bottom = scale_surface_blended(border_bottom, screen_area().w, border_bottom->h)).null())
				{
					WARN_NG << "storyscreen got a null bottom border surface after rescaling\n";
				}
				else
				{
					blur_area(video_, update_area.h, border_bottom->h);
					video_.blit_surface(0, update_area.y + update_area.h, border_bottom);
					update_area.h += border_bottom->h;
				}
			}
		}
	}
#endif

	void part_ui::render_story_box()
	{
		LOG_NG << "ENTER part_ui()::render_story_box()\n";

		const std::string& storytxt = p_.text();
		if (storytxt.empty())
		{
			wait_for_input();
			return;
		}

		const part::BLOCK_LOCATION tbl = p_.story_text_location();
		const int max_width = buttons_x_ - storybox_padding - text_x_;
		const int max_height = screen_area().h - storybox_padding;

		skip_ = false;
		last_key_ = true;

		font::ttext t;
		if (!t.set_text(p_.text(), true))
		{
			ERR_NG << "Text: Invalid markup in '"
				<< p_.text() << "' rendered as is.\n";
			t.set_text(p_.text(), false);
		}
		t.set_font_style(font::ttext::STYLE_NORMAL)
			.set_font_size(storybox_font_size)
			.set_foreground_color(storybox_font_color)
			.set_maximum_width(max_width)
			.set_maximum_height(max_height);
		surface txtsurf = t.render();

		if (txtsurf.null())
		{
			ERR_NG << " storyscreen text area rendering resulted in a null surface\n";
			return;
		}

		int fix_rect_y = text_y_;
		if (fix_rect_y + storybox_padding + txtsurf->h > screen_area().h && tbl != part::BLOCK_TOP)
		{
			fix_rect_y = 
				(screen_area().h > txtsurf->h + 1) ?
				(std::max(0, screen_area().h - txtsurf->h - (storybox_padding + 1))) :
				(0);
		}
		int fix_text_h;
		switch(tbl)
		{
		case part::BLOCK_TOP:
			fix_text_h = std::max(txtsurf->h + 2 * storybox_padding, screen_area().h / 4);	
			break;
		case part::BLOCK_MIDDLE:
			fix_text_h = std::max(txtsurf->h + 2 * storybox_padding, screen_area().h / 3);
			break;
		default:
			fix_text_h = screen_area().h - fix_text_y;
			break;
		}

		SDL_Rect update_area = create_rect(
			0
			, fix_text_y
			, screen_area().w
			, fix_text_h);

		{
			update_locker locker(video_);

			next_button_.hide();
			back_button_.hide();
			play_button_.hide();

#ifndef LOW_MEM
			blur_area(video_, fix_text_y, fix_text_h);
#endif

			draw_solid_tinted_rectangle(
				0, fix_text_y, screen_area().w, fix_text_h,
				storyshadow_r, storyshadow_g, storyshadow_h,
				storyshadow_opacity,
				video_.getSurface());

			render_story_box_border(update_area);

			next_button_.hide(false);
			back_button_.hide(false);
			play_button_.hide(false);
		}

		if (imgs_.empty())
		{
			update_whole_screen();
		}
		else if (update_area.h > 0)
		{
			update_rect(update_area);
		}

		const int scan_height = 1, scan_width = txtsurf->w;
		SDL_Rect scan = create_rect(0, 0, scan_width, scan_height);
		SDL_Rect dstrect = create_rect(text_x_, 0, scan_width, scan_height);
		surface scan_dst = video_.getSurface();
		bool scan_finished = false;
		while(true)
		{
			scan_finished = scan.y >= txtsurf->h;
			if (!scan_finished)
			{
				dstrect.y = fix_text_y + scan.y + storybox_padding;
				video_.blit_surface(dstrect.x, dstrect.y, txtsurf, &scan);
				update_rect(dstrect);
				++scan.y;
			}
			else skip_ = true;

			if (handle_interface()) break;

			if (!skip_ || scan_finished)
			{
				disp_.delay(20);
			}
		}

		draw_solid_tinted_rectangle(
			0, 0, video_.getx(), video_.gety(), 0, 0, 0,
			1.0, video_.getSurface());
	}

	void part_ui::wait_for_input()
	{
		LOG_NG << "ENTER part_ui()::wait_for_input()\n";

		last_key = true;
		skip_ = true;
		while (!handle_interface())
		{}
	}

	bool part_ui::handle_interface()
	{
		bool result = false;

		bool next_keydown = keys_[SDL_SPACE] || keys_[SDLK_RETURN] ||
			keys_[SDLK_KP_ENTER] || keys_[SDLK_RIGHT];
		bool back_keydown = keys_[SDLK_BACKSPACE] || keys_[SDLK_LEFT];
		bool play_keydown = keys_[SDLK_ESCAPE];

		if ((next_keydown && !last_key_) || next_button_.pressed())
		{
			next_button_.release();
			if (skip_)
			{
				ret_ = NEXT;
				result = true;
			}
			else
			{
				skip_ = true;
			}
		}

		if ((play_keydown && !last_key_) || back_button_.pressed())
		{
			back_button_.release();
			ret_ = BACK;
			result = true;
		}

		last_key_ = next_keydown || back_keydown || play_keydown;

		events::pump();
		events::raise_process_event();
		events::raise_draw_event();
		disp_.flip();

		return result;
	}

	part_ui::RESULT part_ui::show()
	{
		if (p_.music().empty() != true)
		{
			sound::play_music_repeatedly(p_.music());
		}

		if (p_.sound().empty() != true)
		{
			sound::play_sound(p_.sound());
		}

		render_background();

		if (p_.show_title())
		{
			render_title_box();
		}

		if (!imgs_.empty())
		{
			if (!render_floating_images())
			{
				return ret_;
			}
		}

		try
		{
			render_story_box();
		}
		catch(utils::invalid_utf8_exception const&)
		{
			ERR_NG << "invalid UTF-8 sequence in story text, skipping part...\n";
		}

		return ret_;
	}
}
